/**
 * This software was produced for the U. S. Government
 * under Contract No. FA8721-09-C-0002, and is
 * subject to the Rights in Noncommercial Computer Software
 * and Noncommercial Computer Software Documentation Clause
 * (DFARS) 252.227-7014 (JUN 1995)
 * 
 * (c) 2009 The MITRE Corporation. All Rights Reserved.
 *
 * SPMIF to OPNET Translator
 * Author: ewalters
 * Created: 1:18:52 PM, Aug 7, 2007
 * MITRE Corporation
 */
package com.mitre.spmif2opnet.analysis;

import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;

/**
 * This class represents the scenarios we keep together because they are linked by 
 *  synchronization nodes, and must therefore be generated together
 * This we "partition" the scenarios into equivalence classes. 
 */
public class ScenarioPartitions {
	// NOTE: We are tracking identifiers down to the scenario level - 
	//  we do not consider node or graph ids - the only important graph is the
	//  top-level, regardless
		
	/**
	 * 	A single partition - only useful when referenced in this context
	 */
	public static class Partition implements Iterable<Identifier> {
		/**
		 * This is the id of the root of the scenario partitioning 
		 * NOTE: we use a simple invariant to determine which 
		 * id represents the root, and this variant may very well change
		 *  as we reinterpret the root - see the code for details
		 */
		private  Identifier _root;
		/**
		 * What are the partitions?
		 */
		private Set<Identifier> _part;
		
		/**
		 * Default constructor
		 */
		public Partition() {
			_part = new HashSet<Identifier>();
			_root = null;
		}
		
		/**
		 * Dump to String
		 * @return the string
		 */
		public String toString() {
			return _part.toString();
		}
		
		/**
		 * Iterator over the identifiers in the partition
		 * @return the iterator
		 */
		public Iterator<Identifier> iterator() {
			return _part.iterator();
		}
		
		/**
		 * Accessor for the root identifier
		 * @return the root
		 */
		public Identifier getRoot() {
			return _root;
		}
		
		/**
		 * Add an identifier to the partition
		 * First invariant: root = first added
		 * @param id the identifier to add
		 */
		public void add(Identifier id) {
			if (_part.isEmpty()) {
				_root = id;
			}
			_part.add(id);
		}
		
		/**
		 * Add many identifiers to the partition
		 * @param ids the collection
		 */
		public void addAll(Collection<? extends Identifier> ids) {
			for (Identifier id : ids) {
				this.add(id);
			}
		}
		
		/**
		 * Get all of the identifiers in the partition
		 * @return the identifiers
		 */
		public Set<Identifier> getPartition() {
			return _part;
		}
	}
	
	/**
	 *  Partitions the identifiers into sets
	 */
	private Set<Partition> _partitions;
	/**
	 * Maps from the identifier to the set the identifier resides in
	 */
	private Hashtable<String, Partition> _dict;
	
	/**
	 * Default constructor
	 */
	public ScenarioPartitions() {
		_partitions = new HashSet<Partition>();
		_dict = new Hashtable<String, Partition>();
	}

	/**
	 * Do we have a set entry for this identifier?
	 * @param id the identifier
	 * @return is there an entry?
	 */
	public boolean hasSetEntry(Identifier id) {
		Identifier gid = this.extractScenarioId(id);
		return _dict.containsKey(gid.toString());
	}

	/**
	 * Add a single-element set for this identifier
	 * @param id the identifier
	 */
	public void addId(Identifier id) {
		Identifier gid = this.extractScenarioId(id);
		if (_dict.containsKey(gid.toString())) return;
		Partition s = new Partition();
		s.add(gid);
		_partitions.add(s);
		_dict.put(gid.toString(), s);
	}

	/**
	 * Union the partitions that contains the two ids
	 * As long as we maintain that the first id1 represents the 
	 *  scenario partition with the desirable root, we'll be fine
	 * @param id1 the first id
	 * @param id2 the second id
	 */
	public void unionIds(Identifier id1, Identifier id2) {
		Identifier gid1 = this.extractScenarioId(id1);
		Identifier gid2 = this.extractScenarioId(id2);
		Partition s1 = _dict.get(gid1.toString());
		if (s1 == null) {
			s1 = new Partition();
			s1.add(gid1);
		} else {
			_partitions.remove(s1);
		}
		Partition s2 = _dict.get(gid2.toString());
		if (s2 == null) {
			s2 = new Partition();
			s2.add(gid2);
		} else {
			_partitions.remove(s2);
		}
		s1.addAll(s2.getPartition());
		for (Identifier s2_id : s2.getPartition()) {
			_dict.put(s2_id.toString(), s1);
		}
		_partitions.add(s1);
	}

	/**
	 * Get all of the partitions
	 * @return the partitions
	 */
	public Set<Partition> getPartitions() {
		return _partitions;
	}
	
	/**
	 * Get the partition that contains this id
	 * @param id the id 
	 * @return the partition set
	 */
	public Partition getEnclosingSet(Identifier id) {
		return _dict.get(id.toString());
	}
	
	/**
	 * Get the scenario id for the scenario
	 * We extract only scenario and project names
	 * @param id the id
	 * @return the stripped id
	 */
	private Identifier extractScenarioId(Identifier id) {
		Identifier new_id = new Identifier();
		new_id.setProjectName(id.getProjectName());
		new_id.setScenarioName(id.getScenarioName());
		return new_id;
	}

	
	/**
	 *  A little bit of unit testing...
	 * @param args
	 */
	public static void main(String[] args) {
		// Set up the 5 ids
		Identifier id1 = new Identifier();
		id1.setProjectName("foo");
		id1.setScenarioName("foo_scen");
		id1.setGraphId("foo_graph");
		id1.setNodeId("foo_node");

		Identifier id2 = new Identifier();
		id2.setProjectName("bar");
		id2.setScenarioName("bar_scen");
		id2.setGraphId("bar_graph");
		id2.setNodeId("bar_node");

		Identifier id3 = new Identifier();
		id3.setProjectName("baz");
		id3.setScenarioName("baz_scen");
		id3.setGraphId("baz_graph");
		id3.setNodeId("baz_node");

		Identifier id4 = new Identifier();
		id4.setProjectName("qux");
		id4.setScenarioName("qux_scen");
		id4.setGraphId("qux_graph");
		id4.setNodeId("qux_node");

		Identifier id5 = new Identifier();
		id5.setProjectName("quux");
		id5.setScenarioName("quux_scen");
		id5.setGraphId("quux_graph");
		id5.setNodeId("quux_node");

		// Add them all separately
		ScenarioPartitions parts = new ScenarioPartitions();
		parts.addId(id1);
		parts.addId(id2);
		parts.addId(id3);
		parts.addId(id4);
		parts.addId(id5);
		
		// Dump out the results
		for (Partition ids : parts.getPartitions()) {
			System.out.println("Set = " + ids.toString());
		}
		
		System.out.println("");

		// Now union together some of these guys
		// {id1} {id2} {id3} {id4} {id5}
		parts.unionIds(id1, id4);
		// {id1, id4} {id2} {id3} {id5}
		parts.unionIds(id2, id5);
		// {id1, id4} {id2, id5} {id3} 
		parts.unionIds(id3, id4);
		// {id1, id4, id3} {id2, id5}  

		// Dump out the results again
		for (Partition ids : parts.getPartitions()) {
			System.out.println("Set = " + ids.toString());
		}
	}
}
